package ar.uba.fi.tp2Unit.framework;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;

import ar.uba.fi.tp2Unit.framework.TestResultDescription.TestResultType;

/**
 * Collects the results of the execution of a test. It distinguishes
 * between successfuls, failures (checked by assertions) and errors
 * (unanticipated problems).
 * 
 * @author grupo12
 */
public class TestResult extends Observable{

	private List<TestResultDescription> results;
	private int successfulTests;
	private int failedTests;
	private int errorTests;
	private long testStartTime;
	private long testEndTime;
	private boolean finished;

	public TestResult() {
		super();
		this.results = new ArrayList<TestResultDescription>();
		this.successfulTests = 0;
		this.failedTests = 0;
		this.errorTests = 0;
		this.testStartTime = System.currentTimeMillis();
		this.testEndTime = System.currentTimeMillis();
		this.finished = false;
	}
	
	public TestResult(Observer observer) {
		this();
		addObserver(observer);
	}	
	
	public int getRunTests() {
		return successfulTests + failedTests + errorTests;
	}
	
	public int getSuccessfulTests() {
		return successfulTests;
	}

	public int getFailedTests() {
		return failedTests;
	}

	public int getErrorTests() {
		return errorTests;
	}

	public void addSuccessful(String testCaseName, String testSuiteName, String description, String testElapsedTime) {
		TestResultDescription testResultDescription = new TestResultDescription(testCaseName, testSuiteName, description, TestResultType.OK, testElapsedTime);
		results.add(testResultDescription);
		successfulTests++;
		setChanged();
		notifyObservers(testResultDescription);
	}

	public void addFail(String testCaseName, String testSuiteName, String description, String testElapsedTime) {
		TestResultDescription testResultDescription = new TestResultDescription(testCaseName, testSuiteName, description, TestResultType.FAIL, testElapsedTime);
		results.add(testResultDescription);
		failedTests++;
		setChanged();
		notifyObservers(testResultDescription);
	}

	public void addError(String testCaseName, String testSuiteName, String description, String testElapsedTime) {
		TestResultDescription testResultDescription = new TestResultDescription(testCaseName, testSuiteName, description, TestResultType.ERROR, testElapsedTime);
		results.add(testResultDescription);
		errorTests++;
		setChanged();
		notifyObservers(testResultDescription);
	}

	public String toString() {
		sortResultsByTestSuiteName();
		String stringResults ="";
		stringResults = appendResults(stringResults);
		stringResults = appendSummary(stringResults);
		return stringResults;	
	}

	private String appendResults(String stringResults) {
		String lastTestSuiteName ="";
		for (TestResultDescription result : results) {
			if(!result.getTestSuiteName().equals(lastTestSuiteName)){
				lastTestSuiteName = result.getTestSuiteName();
				stringResults = appendTestSuiteName(stringResults, lastTestSuiteName, result);
			}
			stringResults = String.format("%s\n%s", stringResults, result.toString());
		}
		return stringResults;
	}

	private String appendTestSuiteName(String stringResults, String lastTestSuiteName, TestResultDescription result) {
		stringResults = String.format("%s\n\n%s", stringResults, result.getTestSuiteName());
		stringResults = appendDivisionLine(stringResults, lastTestSuiteName);
		return stringResults;
	}

	private String appendDivisionLine(String stringResults, String lastTestSuiteName) {
		String line ="";
		for (int i = 0; i < lastTestSuiteName.length(); i++) {
			line+="-";					
		}
		stringResults = String.format("%s\n%s", stringResults, line);
		return stringResults;
	}

	private String appendSummary(String stringResults) {
		stringResults = String.format("%s\n\nSummary:", stringResults);
		stringResults = String.format("%s\n===============", stringResults);
		stringResults = String.format("%s\nRun.......: %s", stringResults, getRunTests());
		stringResults = String.format("%s\nError.....: %s", stringResults, errorTests);
		stringResults = String.format("%s\nFailures..: %s", stringResults, failedTests);
		return stringResults;
	}
	
	public String getSummary(){
		return appendSummary("");
	}

	private void sortResultsByTestSuiteName() {
		Collections.sort(results, new Comparator<TestResultDescription>(){
           		public int compare(TestResultDescription o1, TestResultDescription o2) {
           			return o1.getTestSuiteName().compareTo(o2.getTestSuiteName());
			}});
	}

	public void stopMeasuringTime() {
		this.testEndTime = System.currentTimeMillis();
		finish();
	}

	private void finish() {
		this.finished = true;
		setChanged();
		notifyObservers();
	}
	
	public boolean isFinished(){
		return this.finished;
	}

	public String getElapsedTime() {
		long elapsedTime = this.testEndTime - this.testStartTime;
		Date elapsedDate = new Date(elapsedTime);
		String formattedElapsedDate = new SimpleDateFormat("s.SSS")
				.format(elapsedDate);
		return formattedElapsedDate;
	}

	public List<String> getErrors() {
		return getResultsByType("ERROR");
	}

	public List<String> getFails() {
		return getResultsByType("FAIL");
	}

	public List<String> getOKs() {
		return getResultsByType("OK");
	}

	private List<String> getResultsByType(String type) {
		sortResultsByTestSuiteName();
		List<String> resultsByType = new ArrayList<String>();
		for (TestResultDescription description : results) {
			if(description.getType().equals(type)){
				resultsByType.add(description.toShowableString());
			}
		}
		return resultsByType;
	}
	
	public Map<String, Double> getTestsExecutionTimesInSeconds(){
		Map<String, Double> times = new HashMap<String, Double>();
		for (TestResultDescription description : results) {
			times.put(description.getTestCaseName(), Double.valueOf(description.getElapsedTime()));
		}
		return times;
	}

	public List<TestResultDescription> getOrderedResults(){
		sortResultsByTestSuiteName();
		return this.results;
	}
	
}
